home *** CD-ROM | disk | FTP | other *** search
/ Interactive Web Graphics with Shout 3D / Interactive Web Graphics With Shout 3D.iso / mac / Shout3Ddemo / S3D_2E1.exe / Shout3d_runtime / codebase / applets / ModDunkPanel.java < prev    next >
Text File  |  2000-09-01  |  31KB  |  966 lines

  1. /**    
  2.     Company:        Eyematic Interfaces
  3.     Project:        Shout3D 2.0 Sample Code
  4.     Class:            ModDunkPanel 
  5.     Date:            April 26, 1999
  6.     Description:    Class for Panel that displays  Mod Dunk Game
  7.     (C) Copyright Eyematic Interfaces, Inc. - 1997-2000 - All rights reserved
  8.  */
  9.  
  10. package applets;
  11.  
  12. import java.applet.*;
  13. import java.awt.*;
  14. import java.util.Hashtable;
  15. import java.io.*;
  16. import java.util.Date;
  17. import java.net.URL;
  18. import shout3d.*;
  19. import shout3d.math.*;
  20. import shout3d.core.*;
  21.  
  22. /**
  23.  * ModDunk Game panel 
  24.  * 
  25.  * A little game.
  26.  * Click the mouse down --> Ward Hole will pull his arm back
  27.  * Release the mouse --> Ward Hole will let fly the plunger
  28.  * 
  29.  * The object is to time the release so that the plunger hits the
  30.  * target. If it does, an animation is played in which Jo falls into
  31.  * the dunk tank.
  32.  * 
  33.  * (Note: Cheaters can use the right mouse button to always win)
  34.  * 
  35.  * @author Paul Isaacs
  36.  * @author Jim Stewartson
  37.  * @author Dave Westwood
  38.  */
  39.  
  40. public class ModDunkPanel extends Shout3DPanel implements DeviceObserver {
  41.     
  42.     //{{ Shout3DApplet methods
  43.     /**
  44.      * Constructor
  45.      */
  46.     public ModDunkPanel(Shout3DApplet applet){
  47.         super(applet);
  48.     }
  49.     
  50.     // Pointers to handy nodes
  51.     //
  52.     Transform    throwingArm;         //    USE Warh_Arm1_L
  53.     Interpolator warh_Arm1_R_Interp; // interpolator for Warh_Arm1_R
  54.     Interpolator warh_Arm2_R_Interp; // interpolator for Warh_Arm2_R
  55.     Transform    plungerRoot;         // USE PLUNGER_ROOT
  56.     Transform    stuntPlunger;         // USE STUNT_PLUNGER
  57.     Transform target;                 // Use Prop_Target
  58.     Transform targetMover;             // Use Prop_Target_Mover
  59.  
  60.     // Interpolators for jo
  61.     Interpolator jo_Hip_PosInterp;
  62.     Interpolator jo_Hip_RotInterp;
  63.     Interpolator jo_Leg2_L_RotInterp;
  64.     Interpolator jo_Leg2_R_RotInterp;
  65.     Interpolator jo_Chest_RotInterp;
  66.     Interpolator jo_Arm1_L_RotInterp;
  67.     Interpolator jo_Arm1_R_RotInterp;
  68.         
  69.     Interpolator prop_DivingBoard_RotInterp; //interpolator for Prop_DivingBoard
  70.     Interpolator prop_DivingBoard_PosInterp; //interpolator for Prop_DivingBoard
  71.  
  72.     // Paths to handy paths
  73.     Node  stuntPlungerPath[];
  74.   
  75.     /**
  76.      *
  77.      * This method is automatcially called by the parent class Shout3DPanel
  78.      * at the correct time during initialize().
  79.      * 
  80.      * Subclasses should implement this to perform any custom initialization tasks.
  81.      */
  82.     public void customInitialize() {
  83.         
  84.         // Load the audio for the gong.
  85.         String gongSound    = applet.getParameter("gongSound");
  86.         if (gongSound != null){
  87.             gong = applet.getAudioClip(applet.getCodeBase(), gongSound);
  88.         }
  89.     
  90.         // To make it easy to find interpolators that effect specific 
  91.         // nodes, collect them all into a handy list
  92.         collectInterpolators();
  93.         
  94.         // Disconnect output of all time sensors.
  95.         // Instead, we'll control the fractions of all interpolators
  96.         // through the simulation.
  97.         disconnectAllTimeSensors();
  98.         
  99.         Node testNode;
  100.         // Get pointers to interesting nodes
  101.         
  102.         // WARD HOLE character:
  103.         //
  104.         // Left Upper Arm, to rotate using physics
  105.         if ((throwingArm = (Transform) getNodeByName("Warh_Arm1_L")) == null)
  106.             throw new Shout3DException("could not find node named throwingArm");
  107.         
  108.         //        
  109.         // Right Arm interpolators, to be controlled by simulation.
  110.         if ((testNode = (Transform) getNodeByName("Warh_Arm1_R")) == null)
  111.             throw new Shout3DException("could not find node named Warh_Arm1_R");
  112.         if ((warh_Arm1_R_Interp = findOrientationInterpolator(testNode)) == null)
  113.             throw new Shout3DException("could not find node named Arm1_R_Interp");
  114.         if ((testNode = (Transform) getNodeByName("Warh_Arm2_R")) == null)
  115.             throw new Shout3DException("could not find node named Warh_Arm2_R");
  116.         if ((warh_Arm2_R_Interp = findOrientationInterpolator(testNode)) == null)
  117.             throw new Shout3DException("could not find node named warh_Arm2_R_Interp");
  118.         
  119.         // PLUNGER
  120.         //
  121.         // Transform to use in placing the plunger
  122.         if ((plungerRoot = (Transform) getNodeByName("PLUNGER_ROOT")) == null)
  123.             throw new Shout3DException("could not find node named PLUNGER_ROOT");
  124.         //
  125.         // This keeps track of where the plungerRoot should be
  126.         // placed during the WAITING, WINDUP, and THROWING stages
  127.         if ((stuntPlunger = (Transform) getNodeByName("STUNT_PLUNGER")) == null)
  128.             throw new Shout3DException("could not find node named STUNT_PLUNGER");
  129.         // Get path to the stunt plunger, which will be required to 
  130.         // find transform between worldspace and the plunger
  131.         // root node is stuntPlungerPath[0] and 
  132.         // stuntPlunger is stuntPlungerPath[stuntPlungerPath.length-1]
  133.         Searcher mySearcher = getNewSearcher();
  134.         mySearcher.setNode(stuntPlunger);
  135.         stuntPlungerPath = mySearcher.searchFirst(getScene());
  136.  
  137.         // JO character
  138.         //
  139.         // Find the interpolators affected various nodes in Jo's body
  140.         if ((testNode = (Transform)getNodeByName("Jo_Hip")) == null)
  141.             throw new Shout3DException("could not find node named Jo_Hip");
  142.         jo_Hip_PosInterp = findPositionInterpolator(testNode);
  143.         jo_Hip_RotInterp = findOrientationInterpolator(testNode);
  144.         if ((testNode = (Transform)getNodeByName("Jo_Leg2_L")) == null)
  145.             throw new Shout3DException("could not find node named Jo_Leg2_L");
  146.         jo_Leg2_L_RotInterp = findOrientationInterpolator(testNode);
  147.         if ((testNode = (Transform)getNodeByName("Jo_Leg2_R")) == null)
  148.             throw new Shout3DException("could not find node named Jo_Leg2_R");
  149.         jo_Leg2_R_RotInterp = findOrientationInterpolator(testNode);
  150.         if ((testNode = (Transform)getNodeByName("Jo_Chest")) == null)
  151.             throw new Shout3DException("could not find node named Jo_Chest");
  152.         jo_Chest_RotInterp = findOrientationInterpolator(testNode);
  153.         if ((testNode = (Transform)getNodeByName("Jo_Arm1_L")) == null)
  154.             throw new Shout3DException("could not find node named Jo_Arm1_L");
  155.         jo_Arm1_L_RotInterp = findOrientationInterpolator(testNode);
  156.         if ((testNode = (Transform)getNodeByName("Jo_Arm1_R")) == null)
  157.             throw new Shout3DException("could not find node named Jo_Arm1_R");
  158.         jo_Arm1_R_RotInterp = findOrientationInterpolator(testNode);
  159.         
  160.         // DIVING BOARD
  161.         //
  162.         if ((testNode = (Transform) getNodeByName("Prop_DivingBoard")) == null)
  163.             throw new Shout3DException("could not find node named Prop_DivingBoard");
  164.         prop_DivingBoard_RotInterp = findOrientationInterpolator(testNode);
  165.         prop_DivingBoard_PosInterp = findPositionInterpolator(testNode);
  166.         
  167.         
  168.         // TARGET
  169.         //
  170.         if ((target      = (Transform) getNodeByName("Prop_Target")) == null)
  171.             throw new Shout3DException("could not find node named Prop_Target");
  172.         if ((targetMover = (Transform) getNodeByName("Prop_Target_Mover")) == null)
  173.             throw new Shout3DException("could not find node named Prop_Target_Mover");
  174.         // Calculate center of target
  175.         calculateTargetCenter();
  176.         
  177.         // Register to receive deviceInput
  178.         // Argument "DeviceInput" means to watch for all devices
  179.         addDeviceObserver(this, "DeviceInput", null);        
  180.  
  181.         getRenderer().addRenderObserver(this, null);
  182.     }
  183.  
  184.     /**
  185.      *  Finalize
  186.      */
  187.     protected void finalize() throws Throwable { 
  188.         myInterpolators = null;
  189.         removeDeviceObserver(this,"DeviceInput");
  190.         super.finalize();
  191.     }
  192.     
  193.     //}} Shout3DApplet methods
  194.     
  195.     //{{ DeviceObserver methods
  196.     /**
  197.      * When mouse goes DOWN, Ward Hole's arm starts to move back.
  198.      * When mouse goes UP, the arm is released.
  199.      * 
  200.      * Pressing the 't' or 'T' key toggles the target's swaying motion.
  201.      */
  202.     public boolean onDeviceInput(DeviceInput di, Object userData) {
  203.         if (di.isOfType("MouseInput")) {
  204.             MouseInput mi = (MouseInput) di;
  205.             switch(mi.which) {
  206.             case MouseInput.DOWN: return onMouseDown(mi.x, mi.y, mi.button);
  207.             case MouseInput.UP:   return onMouseUp(mi.x, mi.y, mi.button);
  208.             }
  209.         }
  210.         else if (di.isOfType("KeyboardInput")) {
  211.             KeyboardInput ki = (KeyboardInput) di;
  212.             if (ki.which == KeyboardInput.PRESS) {
  213.                 return onKeyDown(ki.key);
  214.             }
  215.         }
  216.         return false;
  217.     }
  218.     //}} Device response methods
  219.         
  220.     /////////////////////////////////////////////////////////
  221.     // Mod Dunk Game Specific Methods Begins here
  222.     
  223.     /**
  224.      * Mouse goes down
  225.      * 
  226.      * @param x the x position of the mouse down event
  227.      * @param y the y position of the mouse down event
  228.      */
  229.     public boolean onMouseDown(int x, int y, int whichButton){
  230.                 
  231.         // A click always pulls back the arm.
  232.         // Release is timed to occur at releaseTime after the
  233.         // release.
  234.         pullBackArm();
  235.             
  236.         // Pulling back with right mouse lets you win.
  237.         alwaysAWinner = (whichButton == 1);
  238.     
  239.         // This panel always does something on mouse down...
  240.         return true;
  241.     }
  242.     
  243.     /**
  244.      * Mouse goes up.
  245.      * 
  246.      * @param x the x position of the mouse down event
  247.      * @param y the y position of the mouse down event
  248.      */
  249.     public boolean onMouseUp(int x, int y, int whichButton){        
  250.         // release the arm.  
  251.         releaseArm();
  252.         return true;
  253.     }
  254.     
  255.     /**
  256.      * Key goes down.
  257.      * 
  258.      * @param key the key
  259.      */
  260.     public boolean onKeyDown(int key){
  261.         // The 't' key toggles the target motion.
  262.         if (key == 't' || key == 'T'){
  263.             if (isTargetMoving)
  264.                 setTargetMoving(false);
  265.             else
  266.                 setTargetMoving(true);
  267.         }
  268.         // Don't want to stop anything additional from happening, return false
  269.         return false; 
  270.     }
  271.  
  272.     // When right mouse is used to throw, this is set to TRUE
  273.     // and jo always dunks:
  274.     boolean alwaysAWinner = false;
  275.  
  276.     // State of the Thrower
  277.     //
  278.     static final int WAITING    = 0;
  279.     static final int WINDUP    = 1;
  280.     static final int THROWING = 2;
  281.     static final int AIRBORN  = 3;
  282.     int        curThrowerState    = WAITING;
  283.  
  284.     // State of poor little Jo's dunk
  285.     boolean   isNowDunking = false;
  286.     float      DUNK_DURATION = .666f;
  287.  
  288.     // Throwing arm properties
  289.     //
  290.     float    windupAcceleration    = -10;  // Rate at which windup occurs on mouse down
  291.     float    throwCenterAngle    = 4;    // Resting angle of arm when not pulled
  292.     float    throwSpringK        = 35;   // Spring constant that brings arm to center
  293.     float    throwDamperK        = 1;    // Damping constant that affects arm
  294.     float    minWindupAngle        = 1;    // Farthest that arm will retreat when mouse is pressed and held.
  295.  
  296.     // Throwing arm state
  297.     // 
  298.     // current values at any given time.
  299.     float        throwArmAngle    = 4;
  300.     float        throwArmVelo    = 0;
  301.     float        throwArmAccel    = 0;
  302.     // Tuning this number makes the arm go faster/slower when released.
  303.     static final float PLUNGER_VELO_HACK_FACTOR = 1.2f; //1.5f; //2.0f;
  304.  
  305.     // Plunger state
  306.     float        plungerPos[]    = { 0f, 0f, 0f };
  307.     float        plungerVelo[]    = { 0f, 0f, 0f };
  308.     float        plungerAcc[]    = { 0f, -9.8f, 0f };  // gravity downward
  309.  
  310.     float        plungerRotXAngle = 0f;                  // For making 1D rotation easier.
  311.     float        plungerAngVelo    = 0;
  312.     float        plungerAngAcc    = 0;
  313.     boolean    isPlungerTestedPastTarget = false;
  314.  
  315.     // Target Properties
  316.     float        targetCenter[]                 = { 0f, 0f, 0f };
  317.     float        targetRadiusSquared = 0.75f * 0.75f;
  318.     float        targetDepth            = 0.1f;
  319.     boolean     isTargetMoving = false;
  320.     double      targetMotionStartTime = 0;
  321.     float        targetRotOmega = 1;
  322.     float        targetRotAmplitude = 1;      
  323.     float        targetRotPhase = 0;
  324.  
  325.     // Time and timing
  326.     // 
  327.     double    prevTime    = 0;
  328.     double    curTime    = 0;
  329.     float    timeBetweenUpdates    = 1;
  330.     float    maxTimeStep        = 0.1f;
  331.     double    dunkStartTime = 0;
  332.     double                startThrowTime = 0;
  333.     static final float    THROW_TO_RELEASE_TIME = .26f; //.3f;
  334.  
  335.     // Sound
  336.     AudioClip gong;    
  337.     
  338.     /**
  339.      * This is called once per frame, and updates everything
  340.      * based on what's happened so far. 
  341.      */
  342.     void updateGame() {
  343.         // Animate the target
  344.         if (isTargetMoving)
  345.             moveTarget();
  346.         
  347.         // Simulates motion of arm using physics.
  348.         // If AIRBORN, will also simulate trajectory of plunger.
  349.         // The result is a set of positions and orientations that
  350.         // must then be put into the scene graph in the rest of this
  351.         // method
  352.         performSimulation();
  353.         
  354.         // Sets the rotation of the throwing shoulder based on 
  355.         // rotation calculated in performSimulation.
  356.         setThrowingArmRotation();
  357.  
  358.         // Right arm is moved by setting the fraction of an 
  359.         // animation so that it is coordinated with the throwing arm's motion.
  360.         animateFollowerArm();
  361.  
  362.         if (curThrowerState != AIRBORN) {
  363.             // Makes the plunger follow the stuntPlunger, a dummy transform
  364.             // at the end of Ward Hole's arm.
  365.             makePlungerHeldByWarhol();
  366.         }
  367.         else {
  368.             // Places the plunger based on simulated trajectory
  369.             // calculated in performSimulation()
  370.             makePlungerFollowTrajectory();
  371.         }
  372.         
  373.         // Is it time to switch from THROWING to AIRBORN?
  374.         if (curThrowerState == THROWING) {
  375.             if ((curTime - startThrowTime) > THROW_TO_RELEASE_TIME)
  376.                 releasePlunger();
  377.         }
  378.         
  379.         // If this is the frame where the target was hit, start the dunking!
  380.         if (didPlungerHitTarget()) {
  381.             startDunk();
  382.             // This makes the plunger bounce backwards
  383.             plungerPos[2] = targetCenter[2];
  384.             plungerVelo[2] *= -0.5; 
  385.             plungerAngVelo *= -0.5;
  386.             
  387.             // After the first target hit, the
  388.             // target starts animating:
  389.             if (isTargetMoving == false)
  390.                 setTargetMoving(true);
  391.         }
  392.         if (isNowDunking) {
  393.             animateDunk();
  394.         }
  395.     }
  396.  
  397.     /**
  398.      * Toggles whether the target is swaying back and forth.
  399.      */
  400.     public void setTargetMoving(boolean onOff) {
  401.       isTargetMoving = onOff;
  402.       if (onOff) {
  403.           targetMotionStartTime = getClock().getAbsoluteTime();
  404.       }
  405.       else {
  406.           // remember the phase as the time we're at now, so if 
  407.           // target is restarted, things will pick up nicely.
  408.           float rotTime = (float)(targetRotPhase + (curTime - targetMotionStartTime));
  409.           targetRotPhase = rotTime; 
  410.           
  411.       }
  412.     }
  413.     /**
  414.      * Called when the target is moving to advance it based on
  415.      * a sin function and the time passed since it started moving
  416.      */
  417.     void moveTarget() {
  418.       float rotTime = (float)(targetRotPhase + (curTime - targetMotionStartTime));
  419.       float targetAngle = (float) (targetRotAmplitude * Math.sin( targetRotOmega * rotTime));
  420.       // load axis/angle for rotation about z of targetAngle
  421.         targetMover.rotation.getValue()[0] = 0;
  422.         targetMover.rotation.getValue()[1] = 0;
  423.         targetMover.rotation.getValue()[2] = 1;
  424.         targetMover.rotation.getValue()[3] = targetAngle;
  425.         //notify
  426.         targetMover.rotation.setValue(targetMover.rotation.getValue());
  427.     }
  428.  
  429.     /** Simulates motion of arm using physics.
  430.      *  If AIRBORN, will also simulate trajectory of plunger.
  431.      *  The result is a set of positions and orientations that
  432.      *  must then be put into the scene graph in the rest of this
  433.      *  method
  434.      */
  435.     void performSimulation()
  436.     {
  437.         // Save the time since last update. Other methods use this.
  438.         timeBetweenUpdates = (float)(curTime - prevTime);
  439.         
  440.         // Execute simulation in small time steps until up to current time.
  441.         // Required since one large timestep can wreak havoc,
  442.         // especially when de-iconifying after a while.
  443.         float myTimeBetween = timeBetweenUpdates;
  444.         while ( myTimeBetween > 0 ) {
  445.  
  446.             if ( myTimeBetween < maxTimeStep )
  447.                 performSimulationStep(myTimeBetween);
  448.             else
  449.                 performSimulationStep(maxTimeStep);
  450.  
  451.             myTimeBetween = myTimeBetween - maxTimeStep;
  452.         }
  453.  
  454.     }
  455.  
  456.     /** 
  457.      * Moves simulation forward in time by calculating an accelerations,
  458.      * then advancing the positions and velocities accordingly.
  459.      * 
  460.      */
  461.     void performSimulationStep(float deltaTime)
  462.     {
  463.         updateArmAcceleration();
  464.  
  465.         doNumericalIntegration(deltaTime);
  466.  
  467.         constrainThrowingArm();
  468.         constrainPlunger();
  469.     }
  470.  
  471.     /**
  472.      * Depending on the state of the arm, different accelerations are used. 
  473.      */
  474.     void updateArmAcceleration()
  475.     {    
  476.         if (curThrowerState == WAITING) {
  477.             // do nothing
  478.         }
  479.         else if (curThrowerState == WINDUP) {
  480.             // rotate backwards by predetermined acceleration.
  481.             throwArmAccel = windupAcceleration;
  482.         }
  483.         else if (curThrowerState == THROWING ||
  484.                  curThrowerState == AIRBORN) {
  485.             // Calculate acceleration based on standard simple spring/damper equation.
  486.             throwArmAccel = - throwSpringK * (throwArmAngle - throwCenterAngle)
  487.                             - throwDamperK * throwArmVelo;
  488.         }
  489.     }
  490.  
  491.     /**
  492.      * Given starting position/velocity/acceleration, find the position
  493.      * and velocity at the end of the time step.
  494.      * 
  495.      * Note to anyone who knows about Numerical Integration:
  496.      * This is just plain Euler integration, error prone for 
  497.      * 'stiff' simulations.  This simulation is set up to
  498.      * not be stiff, though.
  499.      * 
  500.      */
  501.     void doNumericalIntegration(float deltaTime)
  502.     {
  503.         // Calculate new angular velocity and angle for the throwing arm.
  504.         throwArmVelo = throwArmVelo + throwArmAccel * deltaTime;
  505.         throwArmAngle = throwArmAngle + throwArmVelo * deltaTime;
  506.  
  507.         // Only make the plunger fly if AIRBORN, else it is
  508.         // positioned during makePlungerHeldByWarhol
  509.         if (curThrowerState == AIRBORN) {
  510.             // translation
  511.             plungerVelo[0] = plungerVelo[0] + plungerAcc[0] * deltaTime;
  512.             plungerVelo[1] = plungerVelo[1] + plungerAcc[1] * deltaTime;
  513.             plungerVelo[2] = plungerVelo[2] + plungerAcc[2] * deltaTime;
  514.             plungerPos[0] = plungerPos[0] + plungerVelo[0] * deltaTime;
  515.             plungerPos[1] = plungerPos[1] + plungerVelo[1] * deltaTime;
  516.             plungerPos[2] = plungerPos[2] + plungerVelo[2] * deltaTime;
  517.             // rotation
  518.             plungerAngVelo = plungerAngVelo + plungerAngAcc * deltaTime;
  519.             plungerRotXAngle = plungerRotXAngle + plungerAngVelo * deltaTime;
  520.         }
  521.     }
  522.     
  523.     /**
  524.      * The throwing arm is constrained not to rotate back further than minWindupAngle
  525.      */
  526.     void constrainThrowingArm()
  527.     {
  528.         if (curThrowerState == WINDUP) {
  529.             // constrain arm not to go too far back
  530.             if (throwArmAngle < minWindupAngle) {
  531.                 throwArmAngle = minWindupAngle;
  532.                 // if forcing a stop, immediately decelerate
  533.                 throwArmVelo = 0;
  534.             }
  535.         }
  536.     }
  537.  
  538.     // The boundaries of the playing area.
  539.     // These are hardcoded based on knowledge of the game.
  540.     float LEFT_WALL_X  = -5;
  541.     float RIGHT_WALL_X = 7;
  542.     float NEAR_WALL_Z = -5;
  543.     float FAR_WALL_Z = 6;
  544.     
  545.     /** 
  546.      * The plungers motion is reflected and damped whenever it hits 
  547.      * a wall, the floor, or the target.
  548.      */
  549.     void constrainPlunger()
  550.     {
  551.         if (curThrowerState != AIRBORN)
  552.             return;
  553.         
  554.         boolean flipAngVelo = false;
  555.         if (plungerPos[1] < 0.0f) {
  556.             // Plunger slipped under the floor. 
  557.             
  558.             // Translation:
  559.             //
  560.             // Move up to floor level, reflect and damp the vertical velocity.
  561.             plungerPos[1] = 0.0f;
  562.             plungerVelo[1] = plungerVelo[1] * -.5f;
  563.             //
  564.             // when hitting the floor, friction damps sideways
  565.             plungerVelo[0] = plungerVelo[0] * .95f;
  566.             plungerVelo[2] = plungerVelo[2] * .95f;
  567.             
  568.             // Rotation:
  569.             // when hitting the floor, flip and dampen angVelo
  570.             plungerAngVelo *= -.5f;
  571.             //
  572.             // If plunger is now spinning slowly, start leveling it out 
  573.             // a bit:
  574.             if (Math.abs(plungerAngVelo) < .5) {
  575.                 // First, normalize angle between -PI and +PI
  576.                 while(plungerRotXAngle > 3.1416)
  577.                     plungerRotXAngle -= 6.2832;
  578.                 while(plungerRotXAngle < -3.1416)
  579.                     plungerRotXAngle += 6.2832;
  580.                 // Now, work like a gentle spring towards horizontal.
  581.                 if (plungerRotXAngle > 0f) {
  582.                     plungerAngVelo -= 1 * (plungerRotXAngle - 1.57079);
  583.                 }
  584.                 else {
  585.                     plungerAngVelo -= 1 * (plungerRotXAngle + 1.57079);
  586.                 }    
  587.             }
  588.         }
  589.  
  590.         // If a wall is hit, just reflect and dampen the translational velocity
  591.         // 
  592.         if (plungerPos[0] < LEFT_WALL_X) {
  593.             plungerPos[0] = LEFT_WALL_X;
  594.             plungerVelo[0] = plungerVelo[0] * -.5f;
  595.             // no change in angular velo since flight || to x-walls
  596.         }
  597.         if (plungerPos[0] > RIGHT_WALL_X) {
  598.             plungerPos[0] = RIGHT_WALL_X;
  599.             plungerVelo[0] = plungerVelo[0] * -.5f;
  600.             // no change in angular velo since flight || to x-walls
  601.         }
  602.         if (plungerPos[2] < NEAR_WALL_Z) {
  603.             plungerPos[2] = NEAR_WALL_Z;
  604.             plungerVelo[2] = plungerVelo[2] * -.5f;
  605.             flipAngVelo = true;
  606.         }
  607.         if (plungerPos[2] > FAR_WALL_Z) {
  608.             plungerPos[2] = FAR_WALL_Z;
  609.             plungerVelo[2] = plungerVelo[2] * -.5f;
  610.             flipAngVelo = true;
  611.         }
  612.  
  613.         if (flipAngVelo) {
  614.             plungerAngVelo *= -.5f;
  615.         }
  616.     }
  617.  
  618.     // These are hardcoded based on what looks good
  619.     float RIGHT_ARM_MIN_FRACTION = 0.25f;
  620.     float RIGHT_ARM_MAX_FRACTION = 1.0f;
  621.     /**
  622.      * Right arm is moved by setting the fraction of an 
  623.      * animation so that it is coordinated with the throwing arm's motion.
  624.      */
  625.     void animateFollowerArm()
  626.     {
  627.         // Ramps animation fraction between [RIGHT_ARM_MAX_FRACTION,RIGHT_ARM_MIN_FRACTION]
  628.         // to match the throwing arms' value in the range [throwCenterAngle,minWindupAngle]
  629.         float animationFraction = 0.0f;
  630.         
  631.         if (throwArmAngle > throwCenterAngle) {
  632.             animationFraction = RIGHT_ARM_MIN_FRACTION;
  633.         }
  634.         else if (throwArmAngle < minWindupAngle) {
  635.             animationFraction = RIGHT_ARM_MAX_FRACTION;
  636.         }
  637.         else {
  638.             animationFraction = (throwArmAngle - throwCenterAngle) 
  639.                               / (minWindupAngle - throwCenterAngle);
  640.             animationFraction = RIGHT_ARM_MAX_FRACTION * animationFraction + RIGHT_ARM_MIN_FRACTION;
  641.         }
  642.         
  643.         // Set this fraction in animation of both joints of the right arm.
  644.         warh_Arm1_R_Interp.fraction.setValue(animationFraction);
  645.         warh_Arm2_R_Interp.fraction.setValue(animationFraction);
  646.     }
  647.  
  648.     /**
  649.      * Sets the rotation of the throwing shoulder based on 
  650.      * rotation calculated in performSimulation.
  651.      */
  652.     void setThrowingArmRotation()
  653.     {
  654.         // Load rotation of throwArmAngle, about -x axis into throwingArm
  655.         throwingArm.rotation.getValue()[0] = -1;
  656.         throwingArm.rotation.getValue()[1] = 0;
  657.         throwingArm.rotation.getValue()[2] = 0;
  658.         throwingArm.rotation.getValue()[3] = throwArmAngle;
  659.         throwingArm.rotation.setValue(throwingArm.rotation.getValue());        
  660.     }
  661.     
  662.     /** Edits the plungerRoot (which lives in world space)
  663.      * so that it matches the location and rotation of 
  664.      * stuntPlunger (which lives in the space at the end of warhol's
  665.      * arm)
  666.      */
  667.     void makePlungerHeldByWarhol()
  668.     {
  669.         //Check this to avoid divide by 0
  670.         //Shouldn't happen, but to be safe...
  671.         if (timeBetweenUpdates == 0f)
  672.             return;
  673.         
  674.         // Convert the origin and y direction from
  675.         // stuntPlunger space to world space, matrix by matrix
  676.         //
  677.         Node curNode;
  678.         float p[] = { 0f, 0f, 0f };
  679.         float y[] = { 0f, 1f, 0f };
  680.         float mtx[];
  681.         for (int i = stuntPlungerPath.length - 1; i >=0; i--) {
  682.             if (stuntPlungerPath[i] instanceof Transform) {                
  683.                 mtx = ((Transform) stuntPlungerPath[i]).getMatrix();
  684.                 MatUtil.multVecMatrix( mtx, p );
  685.                 MatUtil.multDirMatrix( mtx, y );
  686.             }
  687.         }
  688.         
  689.         // Set the translation from the transformed point
  690.         plungerRoot.translation.getValue()[0] = p[0];
  691.         plungerRoot.translation.getValue()[1] = p[1];
  692.         plungerRoot.translation.getValue()[2] = p[2];
  693.         //notify
  694.         plungerRoot.translation.setValue(plungerRoot.translation.getValue());
  695.  
  696.         // Set the rotation as a rotation about the x axis, based on how the
  697.         // Calculate the X rotation required to rotate (0,1,0) into 
  698.         // alignment with y
  699.         MatUtil.normalize(y);
  700.         // Get angle between worldspace y and transformed y.
  701.         float dotprod = y[1]; // Dot product with (0,1,0) is just y value.
  702.         plungerRotXAngle = (float) Math.acos(dotprod);
  703.         // Sign of z tells which direction:
  704.         if (y[2] < 0)
  705.             plungerRotXAngle *= -1;
  706.         // Now set the rotation field.
  707.         plungerRoot.rotation.getValue()[0] = 1;
  708.         plungerRoot.rotation.getValue()[1] = 0;
  709.         plungerRoot.rotation.getValue()[2] = 0;
  710.         plungerRoot.rotation.getValue()[3] = plungerRotXAngle;
  711.         plungerRoot.rotation.setValue(plungerRoot.rotation.getValue());
  712.  
  713.         // Update the plunger velocities (translation and rotational)
  714.         // needed to move forward when the hand lets go of the plunger.
  715.         //
  716.         // Calculate based on deltaPos/deltaT, multiplied by a handy factor
  717.         // that lets us change the timing of the plunger so it's easier to 
  718.         // control.
  719.         plungerVelo[0] = (p[0]-plungerPos[0]) * PLUNGER_VELO_HACK_FACTOR / timeBetweenUpdates;
  720.         plungerVelo[1] = (p[1]-plungerPos[1]) * PLUNGER_VELO_HACK_FACTOR / timeBetweenUpdates;
  721.         plungerVelo[2] = (p[2]-plungerPos[2]) * PLUNGER_VELO_HACK_FACTOR / timeBetweenUpdates;
  722.         
  723.         // Match plunger angular velocity to that of arm
  724.         plungerAngVelo = -throwArmVelo;
  725.  
  726.         // Save this to reflect the new position.
  727.         plungerPos[0] = p[0]; plungerPos[1] = p[1]; plungerPos[2] = p[2];
  728.     }        
  729.     
  730.     /**
  731.      *     Places the plunger based on simulated trajectory
  732.      *  calculated in performSimulation()
  733.      */
  734.     void    makePlungerFollowTrajectory(){
  735.             
  736.         plungerRoot.translation.getValue()[0] = plungerPos[0];
  737.         plungerRoot.translation.getValue()[1] = plungerPos[1];
  738.         plungerRoot.translation.getValue()[2] = plungerPos[2];
  739.         //notify
  740.         plungerRoot.translation.setValue(plungerRoot.translation.getValue());
  741.         
  742.         plungerRoot.rotation.getValue()[0] = 1;
  743.         plungerRoot.rotation.getValue()[1] = 0;
  744.         plungerRoot.rotation.getValue()[2] = 0;
  745.         plungerRoot.rotation.getValue()[3] = plungerRotXAngle;
  746.         //notify
  747.         plungerRoot.rotation.setValue(plungerRoot.rotation.getValue());
  748.     }
  749.     
  750.     /**
  751.      * Calculates current worldspace location of target center.
  752.      * Since target may animate, this can be different each frame.
  753.      * This is done based on knowledge that there are only two
  754.      * transforms affecting the target, (target and targetMover)
  755.      */
  756.     void calculateTargetCenter() {
  757.         // Start with 0,0,0 which is center of target,
  758.         targetCenter[0] = 0f;
  759.         targetCenter[1] = 0f;
  760.         targetCenter[2] = 0f;
  761.         // Transform by target's tranfsorm
  762.         MatUtil.multVecMatrix( target.getMatrix(), targetCenter);    
  763.         // Transform by targetMover's transform
  764.         MatUtil.multVecMatrix( targetMover.getMatrix(), targetCenter);
  765.     }
  766.     
  767.     /**
  768.      * Determines if the plunger hit the target during this last time step.
  769.      */
  770.     boolean didPlungerHitTarget() 
  771.     {
  772.         boolean result = false;
  773.         if (isPlungerTestedPastTarget == false) {
  774.             // Only compare if plunger has just past the depth of the target center.
  775.             // Target moves only in xy plane, so this is exactly when the 
  776.             // test needs to be done.
  777.             if (plungerPos[2] < (targetCenter[2] + targetDepth)) {
  778.                 if (alwaysAWinner) {
  779.                     result = true;
  780.                 }
  781.                 else {
  782.                     calculateTargetCenter();
  783.                     // Is the plunger position within the targetRadius of the
  784.                     // target's center?
  785.                     float xdist = plungerPos[0] - targetCenter[0];
  786.                     float ydist = plungerPos[1] - targetCenter[1];
  787.  
  788.                     float dist2d = xdist * xdist + ydist * ydist;
  789.                     if ( dist2d < targetRadiusSquared ) {
  790.                         result = true;
  791.                     }
  792.                 }
  793.                 isPlungerTestedPastTarget = true;
  794.             }
  795.         }
  796.  
  797.         return result;
  798.     }
  799.  
  800.     /**
  801.      * Gets the dunk started
  802.      */
  803.     void startDunk()
  804.     {
  805.         isNowDunking = true;
  806.         dunkStartTime = curTime;
  807.         if (gong != null) gong.play();
  808.     }
  809.  
  810.     /**
  811.      * Determines the fraction to apply to all timers in the dunk animation.
  812.      */
  813.     void animateDunk()
  814.     {
  815.         float fraction = (float)((curTime - dunkStartTime)/ DUNK_DURATION);
  816.         if (fraction > 1.0f)
  817.             fraction = 1.0f;
  818.         if (fraction < 0.0f)
  819.             fraction = 0.0f;
  820.         
  821.         setDunkInterpolators(fraction);
  822.     }
  823.     /**
  824.      * Visits all dunk timers and sets to the given fraction.
  825.      */
  826.     
  827.     void setDunkInterpolators(float fraction)
  828.     {
  829.         jo_Hip_PosInterp.fraction.setValue(fraction);
  830.         jo_Hip_RotInterp.fraction.setValue(fraction);
  831.         jo_Leg2_L_RotInterp.fraction.setValue(fraction);
  832.         jo_Leg2_R_RotInterp.fraction.setValue(fraction);
  833.         jo_Chest_RotInterp.fraction.setValue(fraction);
  834.         jo_Arm1_L_RotInterp.fraction.setValue(fraction);
  835.         jo_Arm1_R_RotInterp.fraction.setValue(fraction);
  836.         
  837.         prop_DivingBoard_RotInterp.fraction.setValue(fraction);
  838.         prop_DivingBoard_PosInterp.fraction.setValue(fraction);
  839.  
  840.     }
  841.  
  842.     /**
  843.      * Called when the mouse first clicks down.
  844.      */
  845.     void    pullBackArm() 
  846.     {
  847.         // change state, initialize prevTime for this turn
  848.         curThrowerState = WINDUP;
  849.         // Reset Dunktank
  850.         isNowDunking = false;
  851.         setDunkInterpolators(0.0f);
  852.     }
  853.  
  854.     /**
  855.      * Called when THROW_RELEASE_TIME has passed since the
  856.      * mouse was released.
  857.      */
  858.     void    releasePlunger()
  859.     {
  860.         // change state, advance prev time
  861.         curThrowerState = AIRBORN;
  862.         // Just released, so we'll want a new shot at target
  863.         isPlungerTestedPastTarget = false;
  864.     }
  865.  
  866.     /**
  867.      * Called when the mouse is released. 
  868.      * The plunger stays attached until 
  869.      * THROW_RELEASE_TIME has passed, then releasePlunger() 
  870.      * will be called during updateGame()
  871.      */
  872.     void    releaseArm()
  873.     {
  874.         curThrowerState = THROWING;        
  875.         startThrowTime = curTime;
  876.     }
  877.  
  878.     /**
  879.      * 
  880.      * Called each frame.  Updates time, then calles updateGame
  881.      * 
  882.      * Note that no onPostRender() method is implemented in this class,
  883.      * even though it is required for all classes implementing RenderObserver.
  884.      * This is because the super class implements RenderObserver fully 
  885.      * and onPostRender is inherited.  
  886.      * Hence this class needs only to override the onPreRender() method, 
  887.      * making sure to call super.onPreRender(r,userData) within the body 
  888.      * of the method.
  889.      * 
  890.      */
  891.     public void onPreRender(Renderer r, Object userData){
  892.         super.onPreRender(r, userData);
  893.         
  894.         boolean wasTimeZero = (curTime == 0);
  895.         prevTime = curTime;    
  896.         curTime = getClock().getAbsoluteTime();
  897.         // System.out.println("wasTimeZero = " + wasTimeZero);
  898.         if ( !wasTimeZero ) {
  899.             updateGame();
  900.         }
  901.     }
  902.     Interpolator[] myInterpolators = null;
  903.     
  904.     // Collects all the interpolators in the scene and puts the list into myInterpolators
  905.     void collectInterpolators() {
  906.         Searcher mySearcher = getNewSearcher();
  907.         mySearcher.setType("Interpolator");
  908.         Node[][] allPaths = mySearcher.searchAll(getScene());
  909.         if (allPaths == null)
  910.             return;
  911.         myInterpolators = new Interpolator[allPaths.length];
  912.         for (int i = 0; i < allPaths.length; i++) {
  913.             if (allPaths[i] == null)
  914.                 myInterpolators[i] = null;
  915.             else
  916.                 myInterpolators[i] = (Interpolator) allPaths[i][allPaths[i].length-1];
  917.         }
  918.     }
  919.  
  920.     TimeSensor tmpSensor;
  921.     void disconnectAllTimeSensors() {
  922.         Searcher mySearcher = getNewSearcher();
  923.         mySearcher.setType("TimeSensor");
  924.         Node[][] allPaths = mySearcher.searchAll(getScene());
  925.         if (allPaths == null)
  926.             return;
  927.         for (int i = 0; i < allPaths.length; i++) {
  928.                 tmpSensor = (TimeSensor) allPaths[i][allPaths[i].length-1];
  929.                 for (int j = tmpSensor.fraction.getNumRoutes()-1; j >= 0; j--){
  930.                     tmpSensor.fraction.deleteRoute(tmpSensor.fraction.getRoutedField(j));
  931.                 }    
  932.         }
  933.     }
  934.         
  935.     // Returns interpolator that affects forNode.
  936.     // Second argument is whether or not to set the timeSensor of the 
  937.     // interpolator to null
  938.     public Interpolator findOrientationInterpolator(Node forNode){
  939.         if ( !(forNode instanceof Transform))
  940.             return null;
  941.         Transform xf = (Transform) forNode;
  942.         
  943.         for (int i = 0; i < myInterpolators.length;i++) {
  944.             if (myInterpolators[i] instanceof OrientationInterpolator){
  945.                 if ( isRouted(((OrientationInterpolator)myInterpolators[i]).value, xf.rotation)){
  946.                     return myInterpolators[i];                    
  947.                 }
  948.             }
  949.         }
  950.         return null;
  951.     }
  952.     public Interpolator findPositionInterpolator(Node forNode){
  953.         if ( !(forNode instanceof Transform))
  954.             return null;
  955.         Transform xf = (Transform) forNode;
  956.         
  957.         for (int i = 0; i < myInterpolators.length;i++) {
  958.             if (myInterpolators[i] instanceof PositionInterpolator){
  959.                 if ( isRouted(((PositionInterpolator)myInterpolators[i]).value, xf.translation)){
  960.                     return myInterpolators[i];                    
  961.                 }
  962.             }
  963.         }
  964.         return null;
  965.     }        
  966. }